package com.flyqiu.flow.core.tools;

import cn.hutool.core.util.IdUtil;
import com.flyqiu.common.tools.FlyQiuCollectionTools;
import com.flyqiu.flow.bpmn.element.BaseProcElement;
import com.flyqiu.flow.bpmn.element.ProcNodeModel;
import com.flyqiu.flow.bpmn.element.SequenceFlow;
import com.flyqiu.flow.common.std.IProIcActivityMove;
import com.flyqiu.flow.core.fliter.BpmnPathFilter;
import lombok.Setter;

import java.util.*;

/**
 * 默认流程活动移动器
 */
public class DefaultProcMove implements IProIcActivityMove {
    @Setter
    private BpmnPathFilter filter;

    private LinkedHashMap<String, List<BaseProcElement>> paths;

    private Runnable applyMove;
    private List<BaseProcElement> realPath;

    @Setter
    private BaseProcElement startElement;
    @Setter
    private BaseProcElement endElement;
    private Map<String, List<?>> oldElementConfigMap;


    public void setMoveReadyListener(Runnable applyMove) {
        this.applyMove = applyMove;
    }


    public void move() {
        if (null == filter) {
            throw new RuntimeException("cannot be filter null");
        }
        if (null == startElement) {
            throw new RuntimeException("cannot be startElement null");
        }
        if (null == endElement) {
            throw new RuntimeException("endElement be startElement null");
        }
        if (null == applyMove) {
            throw new RuntimeException("cannot be applyMove null");
        }
        BpmnPathSearch bpmnPathSearch = new BpmnPathSearch(startElement, endElement);
        bpmnPathSearch.setPathFilter(filter);
        paths = bpmnPathSearch.search();
        realPath = getRealPath();
        try {
            buildNewPath();
            applyMove.run();
        } finally {
            restorePath();
        }

    }


    /**
     * 构建新路径
     */
    private void buildNewPath() {
        oldElementConfigMap = new LinkedHashMap<>();
        if (realPath == null) {
            realPath = new ArrayList<>();
            realPath.add(startElement);
            realPath.add(endElement);
            build(startElement, endElement);
            return;
        }
        int size = realPath.size();
        for (int i = 0; i < size; i++) {
            int nextIndex = i + 1;
            if (nextIndex >= size) {
                return;
            }
            BaseProcElement element = realPath.get(i);
            BaseProcElement flowElement = realPath.get(nextIndex);
            build(element, flowElement);
        }
    }

    /**
     * 构建路径
     *
     * @param start
     * @param end
     */
    private void build(BaseProcElement start, BaseProcElement end) {
        if (startIsSequenceFlow(start, end)) {
            return;
        }
        if (!(start instanceof ProcNodeModel startNode)) {
            throw new RuntimeException("startElement:" + start.getDefKey() + " cannot be converted to " + ProcNodeModel.class.getName());
        }
        List<SequenceFlow> oldSequenceFlows = startNode.getOutgoingFlows();
        List<SequenceFlow> newSequenceFlows;

        if (FlyQiuCollectionTools.isEmpty(startNode.getOutgoingFlows())) {
            newSequenceFlows = createTempSequenceFlows(start, end);
        } else {
            newSequenceFlows = handlerStartLinkEnd(oldSequenceFlows, start, end);
        }
        startNode.setOutgoingFlows(newSequenceFlows);
        oldElementConfigMap.put(startNode.getDefKey(), oldSequenceFlows);
    }

    private List<SequenceFlow> handlerStartLinkEnd(List<SequenceFlow> oldSequenceFlows, BaseProcElement start, BaseProcElement end) {
        Optional<SequenceFlow> first = oldSequenceFlows.stream().filter(e -> e.getDefKey().equals(end.getDefKey())).findFirst();
        if (first.isEmpty()) {
            return createTempSequenceFlows(start, end);
        }
        List<SequenceFlow> newSequenceFlows = new ArrayList<>();
        SequenceFlow sequenceFlow = first.get();
        SequenceFlow newSequenceFlow = getNewSequenceFlow(sequenceFlow);
        newSequenceFlows.add(newSequenceFlow);

        return newSequenceFlows;
    }

    private static SequenceFlow getNewSequenceFlow(SequenceFlow sequenceFlow) {
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setDefName(sequenceFlow.getDefName());
        newSequenceFlow.setDefKey(sequenceFlow.getDefKey());
        newSequenceFlow.setSourceNode(sequenceFlow.getSourceNode());
        newSequenceFlow.setTargetNode(sequenceFlow.getTargetNode());
        newSequenceFlow.setSourceDefKey(sequenceFlow.getSourceDefKey());
        newSequenceFlow.setTargetDefKey(sequenceFlow.getTargetDefKey());
        newSequenceFlow.setSequencesType(sequenceFlow.getSequencesType());

        return newSequenceFlow;
    }

    private List<SequenceFlow> createTempSequenceFlows(BaseProcElement start, BaseProcElement end) {
        List<SequenceFlow> newSequenceFlows = new ArrayList<>();
        SequenceFlow sequenceFlow = new SequenceFlow();
        sequenceFlow.setDefKey("start_temp_sequenceFlow" + IdUtil.simpleUUID());
        sequenceFlow.setDefName("temp_SequenceFlow");
        sequenceFlow.setSourceNode(start);
        sequenceFlow.setTargetNode(end);
        newSequenceFlows.add(sequenceFlow);
        System.out.println("构建路径id:" + sequenceFlow.getDefKey());
        return newSequenceFlows;
    }

    private boolean startIsSequenceFlow(BaseProcElement start, BaseProcElement end) {
        if (!(start instanceof SequenceFlow sequenceFlow)) {
            return false;
        }
        List<BaseProcElement> list = new ArrayList<>();
        list.add(sequenceFlow.getTargetNode());
        sequenceFlow.setTargetNode(end);
        oldElementConfigMap.put(start.getDefKey(), list);
        return true;
    }


    /**
     * 还原旧的路径
     */
    @SuppressWarnings("unchecked")
    private void restorePath() {
        for (BaseProcElement element : realPath) {
            if (!oldElementConfigMap.containsKey(element.getDefKey())) {
                continue;
            }
            List<?> oldConfigs = oldElementConfigMap.get(element.getDefKey());
            if (oldConfigs == null || oldConfigs.isEmpty()) continue;
            if (element instanceof ProcNodeModel procNodeModel) {
                List<SequenceFlow> sequenceFlows = (List<SequenceFlow>) oldConfigs;
                procNodeModel.setOutgoingFlows(sequenceFlows);
            } else {
                SequenceFlow sequenceFlow = (SequenceFlow) element;
                List<BaseProcElement> list = (List<BaseProcElement>) oldConfigs;
                sequenceFlow.setTargetNode(list.get(0));
            }
        }
    }


    private List<BaseProcElement> getRealPath() {
        if (null == paths || paths.isEmpty()) {
            return null;
        }
        Set<Map.Entry<String, List<BaseProcElement>>> entries = paths.entrySet();
        Iterator<Map.Entry<String, List<BaseProcElement>>> iterator = entries.iterator();
        Map.Entry<String, List<BaseProcElement>> next = iterator.next();
        return next.getValue();
    }

    @Override
    public String getStartDefKey() {
        return startElement.getDefKey();
    }

    @Override
    public String getEndDefKey() {
        return endElement.getDefKey();
    }
}
